﻿using myAC.Configuration;
using myAC.DataProviders;
using myAC.FeatureVector;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace myAC.sLDA
{
    public class sLDAInputFileGenerator
    {
        static List<String> classLabels; // callCode sequence number -> classLabel

        
        /// <summary>
        /// Iterate through data instnaces and generate a txt input file for TLC.
        /// The sLDA input file format: http://www.cs.cmu.edu/~chongw/slda/readme.txt
        /// [M] [term_1]:[count] [term_2]:[count] ...  [term_N]:[count]
        /// [M] is the number of unique terms in each document, NOT the TextFeature vocabulary count!
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="instances"></param>
        public static void GenerateInputData(ACInstance[] insts, String filePath, Dictionary<Int32, Int32>[] DTVs, String[] otherFeatureFilter,
            Int32 totalFeatureCount, DTVHelper dtvHelper, Config cfg)
        {
            Console.WriteLine("Total terms in TextFeatures:{0}", dtvHelper.id_term.Count);
            classLabels = GetLabels(insts);

            DateTime minTimeOfAllSRs = insts.Min(srCreatedTime);
            DateTime maxTimeOfAllSRs = insts.Max(srCreatedTime);
            Double timespan = (maxTimeOfAllSRs - minTimeOfAllSRs).TotalSeconds;
            DateTime splitPoint8020 = minTimeOfAllSRs + new TimeSpan(0, 0, (Int32)(timespan * 0.8)); //[----80% train----][--20% test--]

            String fileNamePrefix = DateTime.Now.Ticks.ToString();
            filePath = fileNamePrefix + "." + filePath;

            var train = insts.Where(inst => srCreatedTime(inst) >= minTimeOfAllSRs && srCreatedTime(inst) < splitPoint8020);
            var test = insts.Where(inst => srCreatedTime(inst) >= splitPoint8020 && srCreatedTime(inst) <= maxTimeOfAllSRs);
            Console.WriteLine("Total: {0}", insts.Length);
            Console.WriteLine("Train: {0}", train.Count());
            Console.WriteLine("Test: {0}", test.Count());

            

            String trainFile = "train." + filePath;
            GenerateInputDataInTimeSpan(insts, trainFile, DTVs, otherFeatureFilter, totalFeatureCount, dtvHelper, cfg, minTimeOfAllSRs, splitPoint8020);
            String testFile = "test." + filePath;
            GenerateInputDataInTimeSpan(insts, testFile, DTVs, otherFeatureFilter, totalFeatureCount, dtvHelper, cfg, splitPoint8020, maxTimeOfAllSRs);

        }


        static Func<ACInstance, DateTime> srCreatedTime = inst => DateTime.Parse(inst.OtherFeatures["CreatedTime"].ToString());

        static Func<ACInstance, DateTime, DateTime, Boolean> srCreatedTimeInRange = (inst, start, end) =>
        {
            DateTime createdTime = srCreatedTime(inst);
            return createdTime >= start && createdTime <= end;
        };

        private static void GenerateInputDataInTimeSpan(ACInstance[] insts, String filePath, Dictionary<Int32, Int32>[] DTVs, String[] otherFeatureFilter,
            Int32 totalFeatureCount, DTVHelper dtvHelper, Config cfg, DateTime start, DateTime end)
        {

            StringBuilder sbData = new StringBuilder();
            StringBuilder sbLabel = new StringBuilder();

            if (insts.Length != DTVs.Length)
            {
                Console.WriteLine("Fatal!  instance# != DTVs#");
                return;
            }


            String dataFilePath = "data." + filePath;
            String labelFilePath = "label." + filePath;
            Int32 lineCount = 0;


            //sLDA needs 2 input files <data> file and <label> file.            
            for (Int32 i = 0; i < insts.Length; i++)
            {
                //[M] [term_1]:[count] [term_2]:[count] ...  [term_N]:[count]
                ACInstance inst = insts[i];
                if (srCreatedTimeInRange(inst, start, end))
                {

                    //generate the <data> file.   
                    Dictionary<Int32, Int32> dtv = DTVs[i];

                    Int32 termCountOfADoc_M = dtv.Count;
                    sbData.Append(termCountOfADoc_M); //[M]
                    sbData.Append("\t");

                    foreach (var pair in dtv)
                    {
#if DEBUG
                        String dtvElement = String.Format(@"{0}-[{1}]:{2}", pair.Key, dtvHelper.id_term[pair.Key], pair.Value);
#else
                        String dtvElement = String.Format(@"{0}:{1}", pair.Key, pair.Value);
#endif
                        sbData.Append(dtvElement);
                        sbData.Append("\t");
                    }

                    sbData.Append("\r\n");

                    //generate the <label> file.
                    String callCode = callCodeFunc2(inst);
                    String label = classLabels.IndexOf(callCode).ToString(); // the label must be 0 - (N-1), where N is the total number of labels.
#if DEBUG
                    sb.AppendLine(String.Format("{0}-[{1}]", label, callCode));
#else
                    sbLabel.AppendLine(label);
#endif
                    lineCount++;

                    if (lineCount == 50)
                    {
                        File.AppendAllText(dataFilePath, sbData.ToString());
                        File.AppendAllText(labelFilePath, sbLabel.ToString());
                        sbData.Clear();
                        sbLabel.Clear();
                        lineCount = 0;
                    }
                }
            }

            if (lineCount > 0)
            {
                File.AppendAllText(dataFilePath, sbData.ToString());
                File.AppendAllText(labelFilePath, sbLabel.ToString());
            }
        }



        static Func<myAC.DataProviders.ACInstance, SupportTopic> topicFunc = inst => (SupportTopic)(inst.OtherFeatures["Topic"]);

#if L1
        static Func<myAC.DataProviders.ACInstance, String> callCodeFunc2 = inst => topicFunc(inst).CallCodeIdAtDifferentLevels[0];
#elif L2
        static Func<myAC.DataProviders.ACInstance, String> callCodeFunc2 = inst => topicFunc(inst).CallCodeIdAtDifferentLevels[1];
#else
        static Func<myAC.DataProviders.ACInstance, String> callCodeFunc2 = inst => topicFunc(inst).CallCodeIdAtDifferentLevels[2];
#endif

        private static List<String> GetLabels(ACInstance[] insts)
        {
            List<String> labelSet = insts.GroupBy(callCodeFunc2).Select(group => group.Key).Distinct().ToList();
            Console.WriteLine("Total labels count: {0}", labelSet.Count());
            return labelSet;  // the topics which have no SRs is not included here.
        }
    }

}
